iT邦幫忙

2022 iThome 鐵人賽

DAY 23
1
Modern Web

Hello TypeScript 菜鳥系列 第 23

Day 22. TypeScript Type What?:Type Assertion

  • 分享至 

  • xImage
  •  

今天的文章分成三個子題,會先從Type Assertion (型別斷言) 的基本語法開始看起,然後再一起討論兩個跟Type Assertion相關的 non-null assertion operator ( ! )和 const assertion。

Type Assertion

Type assertion的作用是告訴TypeScript compiler某個變數的型別是我所聲稱的型別,並會忽略compiler推導的型別。

雖然看似有點像Type casting(型別轉換),而且也有很多討論會說type assertion是將某個變數轉型(casting)為其他型別,但實際上type assertion並不會轉換變數的型別

Type assertion的目的是方便開發者告知compiler變數的型別,讓程式能通過型別檢查,避免某些時候compiler沒辦法正確地推導出開發者需要的型別而出現錯誤。

假設有些時候會需要先給一個空物件,然後等到程式跑完或是使用者輸入完相關資訊才會給予物件屬性值:

let user = {};

user.id = 12345678;	// error
user.name = "Bon";	// error

Property 'id' does not exist on type '{}'.(2339)
Property 'name' does not exist on type '{}'.(2339)

給予user物件兩個屬性和屬性值後,compiler可能會因為無法判斷 {} 的型別(應該包含哪些屬性)而出現錯誤。

遇到這個問題時,第一時間可能會認為那就以type annotation的方式,明確指定 user 物件的型別是 User:

interface User{
  id: number;
  name: string;
}

let user: User = {};	// type annotation

user.id = 12345678;
user.name = "Bon";

結果出現

Type '{}' is missing the following properties from type 'User': id, name(2739)

因為type annotation是讓變數一定會是這個型別(而且永遠都是這個型別),當user空物件事先被註記為是User型別,卻不包含User型別應該要有的 idname 屬性,所以compiler又丟出錯誤訊息了...

這種時候就可以用type assertion的方式向TypeScript compiler斷言:

「嘿!我這個物件其實是User型別」

讓compiler不會因為變數不符合compiler自己推導的型別而拋出錯誤訊息。

Type assertion聲稱型別的方式有兩種,一種是用角括號 <> 指定型別:

// 1. <>
interface User{
  id: number;
  name: string;
}

let user = <User>{};	// type assertion

user.id = 12345678;
user.name = "Bon";

但要注意 <> 語法無法在 .tsx 檔案使用,也就是React的檔案中使用,因為會被以為是JSX語法而出現錯誤。

所以有另一種方法是用 as 關鍵字:

// 2. as
interface User{
  id: number;
  name: string;
}

let user = {} as User;	// type assertion

user.id = 12345678;
user.name = "Bon";

還有幾個關於type assertion的事項可以提一下:

  1. 不能強迫把一個很明確的變數型別聲稱為另一個型別,這點也證明type assertion不能轉型,例如:
    let oneStr = "1" as number; // error
  2. 若一次type assertion不行的話,就做兩次type assertion吧!例如:
    let foo = (express as any) as T;
  3. Type assertion只會在編譯期間(compile time)的型別確認有作用,並不會在執行期間(run time)有任何的檢查的行為,意思是,如果程式有出錯執行時期也不會拋出錯誤或出現 null,例如以下範例的user物件少給了一個屬性:
interface User{
  id: number;
  name: string;
}

let user = {} as User;

user.id = 12345678;	// no error!!!!!

因為第3點的關係,有些人認為type assertion削弱了TypeScript原本強調的type safty (型別安全性),要儘量避免使用type assertion。

第3點相關的案例和討論可以參考以下兩篇文章:


Non-null Assertion Operator ( ! )

! 運算子是當開發者確定變數值有不會是 nullundefined 的情況時使用。

例如下面範例刻意加上了一個 toString 方法,而且因為有預設值 "enter message",所以型別允許有 null 的情況:

function showMessage({msg="enter message"}: {msg?: string | number | null} = {}) {
	alert(msg.toString());
}

showMessage();

不過這個狀況下,compiler認為如果輸入參數是 null 的時候不會有 toString 方法,就會報錯:

'msg' is possibly 'null'.(18047)

因為有預設值,所以這時候可以加上 !,向 compiler 斷言輸入值不會有是 null 情況:

function showMessage({msg="enter message"}: {msg?: string | number | null} = {}) {
	alert(msg!.toString());
}

showMessage();	// hi

Const Assertion

終於到最後一個子題 ─ const assertion。

Const assertion其實就是前面Type Assertion的特例,可以對literal value 斷言為 const

根據官方的const assertion定義,const assertion語法如下,而且會有如註解的效果:

// 1. x 的 type 會是 "x" 且變成 const 變數
let x = "x" as const;

const y = "y"	// y是string型別

// 2. arr 變成 readonly tuple
const arr = [9, 10] as const;	

// 3. nine 和 ten 都是 readonly 屬性
const obj = {nine: 9, ten: 10} as const;

而React .jsx 檔案以外的地方同樣可以用 <> 寫成:

// 1.
let x = <const>"x";

// 2.
const arr = <const>[9, 10];	

// 3.
const obj = <const>{nine: 9, ten: 10};

在案例1,和建立const變數 const y = "y" 不一樣的是,變數x的型別會是"x",而變數y的型別則是string。

如果是對陣列(array)作const assertion,則案例2中的陣列arr會變成只有兩個元素9和10且只能讀取元素值的tuple。

個人認為const assertion最好用的地方是在對物件(object)作const assertion。和JavaScript語法一樣,在TypeScript建立一般的物件常數 const obj ,其實只是不能改變物件參考的記憶體位址,而記憶體位址指向的物件內容還是可以修改的:

const obj = {nine: 9, ten: 10};

obj.nine = 99;	// ok

但如果是對物件作const assertion,則該物件的屬性等於是都加上 readonly,也就無法修改物件的屬性了:

const obj = {nine: 9, ten: 10} as const;

obj.nine = 99;	// error: Cannot assign to 'nine' because it is a read-only property.(2540)

最後補充一篇stackoverflow的問答,這位網友有舉一個例子說明「對array作const assertion,能避免rest parameter可能會出現的錯誤」,有興趣的人可以看看這位網友的例子。


參考資料
Type Assertions @TypeScript Handbook
Type Assertions @TypeScript Tutorial
Explain Type assertions in TypeScript @GeeksforGeeks
Non-null Assertion Operator (Postfix!) @TypeScript Handbook
const assertions @TypeScript 3.4 relaease note


上一篇
Day 21. TypeScript Type What?:Type Alias
下一篇
Day 23. TypeScript Type What?:Type Predicates
系列文
Hello TypeScript 菜鳥31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言